#!/usr/bin/python2.4
#
# Copyright 2009 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The app's primary interface to AppEngine's datastore."""

# AppEngine
from google.appengine.ext import db

# Python
import datetime
import logging

# Maximun allowable string length
_MAX_STRING_LENGTH = 500

# Maximum allowable link length
_MAX_LINK_LENGTH = 2083


def ValidateStringLenth(value, max_length=_MAX_STRING_LENGTH):
  """Checks if the given string is too long.

  Args:
    value: user input to be checked
    max_length: int corresponding to the maximum length of the given |value|

  Returns:
    Boolean: True if acceptable, False if too long.
  """
  if isinstance(value, basestring):
    if len(value.encode('utf-8')) <= max_length:
      return True
  return False

def ValidateEntity(entity_class, params, list_delimiter=None):
  """Given an entity and a user's request params, validates the data.

  Args:
    entity_class: entity class
    params: dictionary params from user's form request.
    list_delimiter: string defining the character that should split a string
        into a list.

  Returns:
    response: dictionary with the appropriate corresponding values
    warnings: dictionary of warnings associated with the user's supplied data
  """
  response = {}
  warnings = {}
  entity = False
  entity_name = entity_class.kind().lower()

  if entity_name in params:
    try:
      if isinstance(params[entity_name], basestring):
        entity = entity_class.get(params[entity_name])

      assert isinstance(entity, entity_class)
      response[entity_name] = entity
    except AssertionError:
      response[entity_name] = params[entity_name]
      warnings[entity_name] = True

  entity_properties = entity_class.properties()
  for prop in entity_properties:
    property = entity_properties[prop]
    if prop not in params and entity:
      # Bools are a special case where a user's request will not be in the
      # params because of the nature of HTML form checkboxes.
      if isinstance(property, db.BooleanProperty):
        response[prop] = False
      continue

    elif prop not in params and not entity:
      response[prop] = property.default_value()
      if property.required:
        warnings[prop] = True
      continue

    else:
      user_input = params[prop]

    warning = False

    try:
      # String
      if isinstance(property, db.StringProperty):
        assert(ValidateStringLenth(user_input))
        property.validate(user_input)

      # ByteString
      elif isinstance(property, db.ByteStringProperty):
        assert(len(user_input) <= _MAX_STRING_LENGTH)
        property.validate(user_input)

      # Boolean
      elif isinstance(property, db.BooleanProperty):
        if user_input in [False, 'False', 'false', '0', 0]:
          user_input = False
        user_input = bool(user_input)

      # Integer
      elif isinstance(property, db.IntegerProperty):
        user_input = int(user_input)
        property.validate(user_input)

      # Float
      elif isinstance(property, (db.FloatProperty)):
        user_input = float(user_input)

      # DateTime
      elif isinstance(property, db.DateTimeProperty):
        if not property.auto_now or not property.auto_now_add:
          if isinstance(user_input, basestring):
            # Expected string format: 19780716223010
            user_input = datetime.datetime.strptime(user_input, '%Y%m%d%H%M%S')
          property.validate(user_input)

      # Date
      elif isinstance(property, db.DateProperty):
        if not property.auto_now or not property.auto_now_add:
          if isinstance(user_input, basestring):
            # Expected string format: 19780716
            year = int(user_input[:4])
            month = int(user_input[4:6])
            day = int(user_input[6:8])
            user_input = datetime.date(year, month, day)
          property.validate(user_input)

      # Time
      elif isinstance(property, db.TimeProperty):
        if not property.auto_now or not property.auto_now_add:
          if isinstance(user_input, basestring):
            # Expected time format: 223010
            h = user_input[:2]
            m = user_input[2:4]
            s = user_input[4:6]
            user_input = datetime.time(h, m, s)
          property.validate(user_input)

      # List
      elif isinstance(property, (db.ListProperty, db.StringListProperty)):
        if isinstance(user_input, basestring):
          if list_delimiter:
            user_input = user_input.split(list_delimiter)

        if not isinstance(user_input, list):
          user_input = [user_input]

        property.validate(user_input)

      # Reference and SelfReference
      elif isinstance(property,db.ReferenceProperty):
        if isinstance(user_input, basestring):
          user_input = db.Model.get(user_input)

        assert user_input.kind() == property.reference_class.kind()

      # User
      elif isinstance(property, db.UserProperty):
        if isinstance(user_input, basestring):
          user = User.get(user_input)
          user_input = user.gia
        property.validate(user_input)

      # Blob
      elif isinstance(property, db.BlobProperty):
        property.validate(user_input)

      # Text
      elif isinstance(property, db.TextProperty):
        property.validate(user_input)

      # Category
      elif isinstance(property, db.CategoryProperty):
        assert(ValidateStringLenth(user_input))
        property.validate(user_input)

      # Link
      elif isinstance(property, db.LinkProperty):
        if user_input:
          assert(ValidateStringLenth(user_input, _MAX_LINK_LENGTH))
          property.validate(user_input)
        else:
          user_input = None

     # Email
      elif isinstance(property, db.EmailProperty):
        assert(ValidateStringLenth(user_input))
        property.validate(user_input)

      # GeoPt
      elif isinstance(property, db.GeoPtProperty):
        property.validate(user_input)

      # IM
      elif isinstance(property, db.IMProperty):
        assert(ValidateStringLenth(user_input))
        property.validate(user_input)

      # PhoneNumber
      elif isinstance(property, db.PhoneNumberProperty):
        assert(ValidateStringLenth(user_input))
        property.validate(user_input)

      # PostalAddress
      elif isinstance(property, db.PostalAddressProperty):
        assert(ValidateStringLenth(user_input))
        property.validate(user_input)

      # Rating
      elif isinstance(property, (db.IntegerProperty, db.RatingProperty)):
        user_input = int(user_input)
        property.validate(user_input)

      # Any property we don't know about
      else:
        assert False

    except:
      # There are a number of reasons an exception can occur here:
      # * When an assertion error is raised when a string is too long
      # * When a string cannot be converted to the correct type (bool, int, etc)
      # * When the user input does not pass the property's validation method
      logging.error("""
                    Property: %s
                    User input: %s'
                    User input type: %s""" % (str(property), str(user_input),
                                              str(type(user_input))))
      warning = True

    response[prop] = user_input
    if warning:
      warnings[prop] = True

  return response, warnings


# Entity classes
class User(db.Model):
  """User entity."""
  gia = db.UserProperty(required=True)
  nickname = db.StringProperty(default='Anonymous')
  url = db.LinkProperty()
  is_trusted = db.BooleanProperty(default=False)
  is_bannded = db.BooleanProperty(default=False)


class Post(db.Model):
  """Post entity."""
  author = db.ReferenceProperty(User, required=True)
  body = db.TextProperty(required=True)
  title = db.StringProperty(required=True)
  comment_count = db.IntegerProperty(default=0)
  is_published = db.BooleanProperty(default=False)
  publish_date = db.DateTimeProperty(default=None)
  edit_date = db.DateTimeProperty(auto_now=True)
  creation_date = db.DateTimeProperty(auto_now_add=True)
  # Meta info for this post
  path = db.StringProperty(default='')
  canonical = db.StringProperty(default='')
  keywords = db.TextProperty(default='')
  description = db.TextProperty(default='')
  noarchive = db.BooleanProperty(default=False)
  noodp = db.BooleanProperty(default=False)
  noindex = db.BooleanProperty(default=False)
  nofollow = db.BooleanProperty(default=False)
  nosnippet = db.BooleanProperty(default=False)


class Comment(db.Model):
  """Comment entity."""
  author = db.ReferenceProperty(User, required=True)
  body = db.TextProperty(required=True)
  date_time = db.DateTimeProperty(auto_now_add=True)
  rating = db.RatingProperty(default=0)
  status = db.StringProperty(choices=set(['approved',
                                          'rejected',
                                          'unreviewed']),
                             default='unreviewed')
  post = db.ReferenceProperty(Post, required=True)
  reply_to = db.SelfReferenceProperty()


class Link(db.Model):
  """Link entity."""
  anchor = db.StringProperty(required=True)
  url = db.LinkProperty(required=True)
  nofollow = db.BooleanProperty(default=False)


class Page(db.Model):
  """Page entity."""
  author = db.ReferenceProperty(User, required=True)
  updated_by = db.ReferenceProperty(User, collection_name='updated_by')
  title = db.StringProperty(required=True)
  button_name = db.StringProperty(required=True)
  path = db.StringProperty(default='')
  body = db.TextProperty(required=True)
  is_published = db.BooleanProperty(default=False)
  publish_date = db.DateTimeProperty()
  edit_date = db.DateTimeProperty(auto_now=True)
  draft_date = db.DateTimeProperty(auto_now_add=True)
  # Meta info for this page
  keywords = db.TextProperty()
  description = db.TextProperty()
  noarchive = db.BooleanProperty(default=False)
  noodp = db.BooleanProperty(default=False)
  noindex = db.BooleanProperty(default=False)
  nosnippet = db.BooleanProperty(default=False)
  nofollow = db.BooleanProperty(default=False)
  canonical = db.StringProperty()


class Blog(db.Model):
  """Blog entity (should only ever be one entity of this type)."""
  name = db.StringProperty(required=True, default='My Blog...')
  tagline = db.StringProperty(default='...about important things.')
  button_name = db.StringProperty(required=True, default='Blog')
  logo_path = db.StringProperty(default='')
  favicon_path = db.StringProperty(default='')
  template = db.StringProperty(default='default', required=True)
  # Meta info for this blog
  keywords = db.TextProperty(default='')
  description = db.TextProperty(default='')
  posts_per_page = db.IntegerProperty(default=5)
  partial_posts = db.BooleanProperty(default=False)
  noarchive = db.BooleanProperty(default=False)
  noodp = db.BooleanProperty(default=False)
  noindex = db.BooleanProperty(default=False)
  nofollow = db.BooleanProperty(default=False)
  nosnippet = db.BooleanProperty(default=False)
  canonical = db.StringProperty(default='')
  enable_comments = db.BooleanProperty(default=True)
  enable_postinfo = db.BooleanProperty(default=True)
  enable_pageinfo = db.BooleanProperty(default=True)
  link_title = db.StringProperty(default='Links', required=True)
  google_cse_box = db.TextProperty(default='')

class DataStore(object):
  """Main class for interfacing with AppEngine's datastore."""

  # TODO (cpatrick): Rethink SetEntity methods, return values are not consistent

  def __init__(self, user_module):
    """Constructs and sets basic instance variables.

    Also create's a User entity if one does not exist and the user is logged in.

    Args:
      user_module: AppEngine's users module.
    """
    self._gia_user = user_module.GetCurrentUser()
    self._admin = user_module.IsCurrentUserAdmin()

    user = None
    if self._gia_user:
      user = User.gql('WHERE gia = :1', self._gia_user).get()
      if not user:
        user = User(gia = self._gia_user)
        if self._admin:
          user.nickname = self._gia_user.nickname()
        user.put()

    self._user = user
    self._datetime = datetime.datetime

  def _GetEntityParams(self, entity, params):
    """Given an entity and user's params, strips out unneeded data.

    Args:
      entity: entity class
      params: dictionary of the request params

    Returns:
      dictionary with only params relevant to the entity.
    """
    # TODO (cpatrick): Not sure this method is even needed.
    if not params:
      return None
    new_params = {}
    entity_name = entity.kind().lower()
    entity_properties = [property for property in entity.properties()]
    for param in params:
      if param in entity_properties:
        new_params[param] = params[param]

    if entity_name in params and params[entity_name]:
      new_params[entity_name] = params[entity_name]

    return new_params

  def _Pathify(self, string):
    """Given a string, turns returns something that can be used as a path.

    Args:
      string: string to be turned into a path

    Returns:
      string without spaces or character that shouldn't be used for general path
          names.
    """
    # Remove leading & trailing whitespace
    string = string.strip()
    # Replace regular url path characters
    string = string.replace('?', '-')
    string = string.replace('=', '-')
    string = string.replace('&', '-')
    # Replace slashes
    string = string.replace(' / ', '-')
    string = string.replace('/', '-')
    # Replace remaining spaces with a -
    string = string.replace(' ', '-')
    # Now make lower case
    string = string.lower()
    if string.startswith('-'):
      string = string[1:]
    if string.endswith('-'):
      string = string[:-2]
    return string

  # Manage User
  def IsUserAdmin(self):
    """Returns if user is an admin."""
    return self._admin

  def GetGiaUser(self):
    """Returns AppEngine's user object for the current user."""
    return self._gia_user

  def GetCurrentUser(self):
    """Returns the current user."""
    return self._user

  def GetUser(self, key):
    """Given a user key, returns the user."""
    return User.get(key)

  def SetUser(self, params):
    """Creates or updates a user.

    Args:
      params: dictionary of params for creating or editing a user.

    Returns:
      user entity object
      dictionary of warnings with user params
    """
    user_params = self._GetEntityParams(User, params)
    user_entity, warnings = ValidateEntity(User, user_params)
    if warnings:
      return user_entity, warnings

    if 'user' in user_entity:
      entity = user_entity['user']
    else:
      # All required properties are need when creating the entity
      entity = User(gia = user_entity['gia'])

    # Changes to a user can only be made by the user or an admin
    if not self._admin and entity.key() != self._user.key():
      return None, None

    for property in entity.properties():
      if property in user_entity:
        # All admin defined properties here
        if property in ['is_trusted', 'is_banned'] and not self._admin:
          continue
        setattr(entity, property, user_entity[property])

    entity.put()
    return entity, None

  def DeleteUser(self, key):
    """Given a key to a user entity, deletes the user entity."""
    if not self._admin:
      return False
    user = User.get(key)
    if user:
      user.delete()
      return True

    return False

  # Manage Post
  def GetPost(self, key):
    """Given a key to a post, returns a post entity."""
    return Post.get(key)

  def GetPublishedPosts(self, offset=0, limit=5):
    """Gets published posts using the supplied limit and offset.

    Args:
      offset: int corresponding the desired entity offset
      limit: int corresponding to the max entities that should be returned by
          the query.
    """
    query = """WHERE is_published = :1
        ORDER BY publish_date DESC
        LIMIT %s
        OFFSET %s""" % (int(limit), int(offset))
    return Post.gql(query, True)

  def GetAllPublishedPosts(self):
    """Returns all published posts."""
    # TODO (cpatrick): This will get out of hand when a lot of post have been
    # written. Consider adding offset and limits.
    all_posts = Post.all()
    published_posts = []
    for post in all_posts:
      if post.is_published:
        published_posts.append(post)

    return published_posts

  def SetPost(self, params):
    """Creates or updates a post.

    Args:
      param: dictionary of request params

    Returns:
      post entity object
      warnings dictionary of warnings
    """
    if not self._admin:
      return None, None

    post_params = self._GetEntityParams(Post, params)

    if not post_params:
      return None, None

    post_entity, warnings = ValidateEntity(Post, post_params)

    if warnings:
      return post_entity, warnings

    is_new_post = False
    if 'post' in post_entity:
      entity = post_entity['post']
    else:
      entity = Post(author = post_entity['author'],
                    body = post_entity['body'],
                    title = post_entity['title'])
      is_new_post = True

    # Only the author of a post can edit it.
    if entity.author.key() != self._user.key():
      return post_entity, {'gia': True}

    # We have some extra work to do for a post's path
    path =  post_entity.get('path')
    if path:
      new_path = self._Pathify(path)
    elif not entity.path:
      new_path = self._Pathify(entity.title)
    else:  # Really this is an existing path
      new_path = entity.path

    # Force the path to be unique
    existing_posts = self.GetPostsByPath(new_path)
    if existing_posts.count():
      path_warning = False
      if is_new_post:
        path_warning = True
      else:
        for post in existing_posts:
          if post.key() != entity.key():
            path_warning = True

      if path_warning:
        return post_entity, {'path': path_warning}
    entity.path = new_path

    # We also have to do some extra work with is_published
    if 'is_published' in post_entity:
      entity.is_published = post_entity['is_published']
      if post_entity['is_published'] and not entity.publish_date:
        entity.publish_date = self._datetime.now()
    else:
      entity.is_published = False

    # The rest we can handle normally
    for property in entity.properties():
      if property in post_entity:
        if property in ['path', 'is_published', 'publish_date']:
          continue
        setattr(entity, property, post_entity[property])

    entity.put()
    return entity, None

  def GetPostsByPath(self, path):
    """Given a path returns the associated post."""
    return Post.gql('WHERE path = :1', path)

  def GetAllPosts(self):
    """Returns all posts sorted by publish date."""
    return Post.gql('ORDER BY publish_date DESC')

  def GetPostsForDates(self, start, end, is_published=True, limit=10, offset=0):
    """Returns the posts for a given date range.

    Args:
      start: datetime object for the starting date range
      end: datetime object for the ending date range
      is_published: boolean corresponding to if the post is published or not
      limit: int max results to resturn
      offset: int starting offset

    Returns:
      gql query object
    """
    query = """WHERE is_published = :1 AND
        publish_date >= :2 AND
        publish_date < :3
        ORDER BY publish_date DESC
        LIMIT %s
        OFFSET %s""" % (int(limit), int(offset))
    return Post.gql(query, is_published, start, end)

  def DeletePost(self, key):
    """Deletes post with the given key.

    Args:
      key: key to the corresponding post.
    """
    if self._admin:
      post = Post.get(key)
      if post:
        post.delete()
        return True

    return False

  # Manage Blog
  def GetBlog(self):
    """Returns the info for this blog. If none creates a default blog."""
    blog = Blog.all()
    if blog.count():
      return blog[0]
    else:
      # Create a default blog, because one doesn't exist
      blog = Blog()
      blog.put()
      return blog

  def SetBlog(self, params):
    """Updates the blog info.

    Args:
        params: dictionary of request params.

    Returns:
      blog entity object
      warnings dictionary
    """
    if not self._admin:
      return None, None

    blog_params = self._GetEntityParams(Blog, params)

    if not blog_params:
      return None, None

    blog_entity, warnings = ValidateEntity(Blog, blog_params)

    if warnings:
      return blog_entity, warnings

    # One blog to rule them all
    entity = Blog.all()[0]

    for property in entity.properties():
      if property in blog_entity:
        user_input = blog_entity[property]
        setattr(entity, property, blog_entity[property])

    entity.put()
    return entity, None

  # Manage Links
  def GetLinks(self):
    """Returns all stored links."""
    return Link.all()

  def GetLink(self, key):
    """Gets a link for the given key.

    Args:
      key: key correspondig to the desired link.

    Returns:
      link entity
      warnings dictionary
    """
    return Link.get(key)

  def SetLink(self, params):
    """Creates or updates a link.

    Args:
      params: dictionary of the user's params

    Returns:
      link entity
      warnings dictionary
    """
    if not self._admin:
      return None, None

    link_params = self._GetEntityParams(Link, params)

    if not link_params:
      return None, None

    link_entity, warnings = ValidateEntity(Link, link_params)

    if warnings:
      return link_entity, warnings

    if 'link' in link_entity:
      entity = link_entity['link']
    else:
      entity = Link(anchor = link_entity['anchor'],
                    url = link_entity['url'])

    for property in entity.properties():
      if property in link_entity:
        user_input = link_entity[property]
        setattr(entity, property, link_entity[property])

    entity.put()
    return entity, None

  def DeleteLink(self, key):
    """Given a key deletes the appropriate link.

    Args:
      key: key the desried link
    """
    if self._admin:
      link = self.GetLink(key)
      if link:
        link.delete()

  # Manage Comment
  def GetComments(self):
    """Returns all comments."""
    return Comment.all()

  def GetComment(self, key):
    """Gets a specific comment.

    Args:
      key: key of the desired comment.

    Returns:
      gql object
    """
    return Comment.get(key)

  def SetComment(self, params):
    """Creates a new comment.

    Args:
      params: dictionary containing the user's input

    Returns:
      comment entity
      warnings dictionary
    """
    if not self._user:
      return None, None

    comment_entity, warnings = ValidateEntity(Comment, params)
    if warnings or comment_entity['author'].key() != self._user.key():
      return comment_entity, warnings

    entity = Comment(author = comment_entity['author'],
                body = comment_entity['body'],
                post = comment_entity['post'])

    for property in entity.properties():
      if property in comment_entity:
        user_input = comment_entity[property]
        setattr(entity, property, comment_entity[property])

    if self._admin:
      entity.status = 'approved'
    entity.put()
    if entity.status == 'approved':
      self.UpdateCommentCount(entity.post)
    return entity, None

  def GetApprovedCommentsForPost(self, post):
    """Given a post, returns all the approved comments.

    Args:
      post: post entity

    Returns:
      gql query object
    """
    return Comment.gql('WHERE post = :1 AND status = \'approved\'', post)

  def UpdateCommentCount(self, post):
    """Given a post, updates the approved comment count.

    Args:
      post: post entity
    """
    comment_count = self.GetApprovedCommentsForPost(post).count()
    post.comment_count = comment_count
    post.put()

  def GetUnReviewedComments(self):
    """Returns unreviewed comments."""
    return Comment.gql('WHERE status = \'unreviewed\'')

  def UpdateCommentStatus(self, key, status):
    """Given a comment key, updates the status.

    Args:
      key: key for the desired comment
      status: string of the new status
    """
    if not self._admin:
      return
    comment = self.GetComment(key)
    comment.status = status
    comment.put()
    if comment.status == 'approved':
      self.UpdateCommentCount(comment.post)

  def DeleteComment(self, key):
    """Given a comment key, deletes the comment.

    Args:
      key: key for the desired comment.
    """
    comment = self.GetComment(key)
    if comment.author.key() == self._user.key() or self._admin:
      comment.delete()

  def GetCommentsForPost(self, key):
    """Given a post key, returns all the comments.

    Args:
      key: key for the desired post

    Returns:
      gql query object
    """
    return Comment.gql('WHERE post = :1', key)

  def GetCommentsByUsers(self, users):
    """Given a list of users, returns all of their comments.

    Args:
      users: list of user entities

    Returns:
      gql query object
    """
    return Comment.gql('WHERE author IN :1', users)

  def GetCommentsForPosts(self, posts):
    """Given a list of posts, returns all of their comments.

    Args:
      posts: list of post entities

    Returns:
      gql query object
    """
    return Thread.gql('WHERE post IN :1', posts)

  # Manage Page
  def GetPages(self):
    """Returns all pages."""
    return Page.all()

  def GetPage(self, key):
    """Given a key, returns the page.

    Args:
      key: key for the corresponding page

    Returns:
      gql query object
    """
    return Page.get(key)

  def SetPage(self, params):
    """Creates or updates a page entity.

    Args:
      params: a dictionary of of user's input

    Returns:
      page entity
      warnings dictionary
    """
    if not self._admin:
      return None, None

    page_params = self._GetEntityParams(Page, params)

    if not page_params:
      return None, None

    page_entity, warnings = ValidateEntity(Page, page_params)

    if warnings:
      return page_entity, warnings

    is_new_page = False
    if 'page' in page_entity:
      entity = page_entity['page']
    else:
      entity = Page(author = page_entity['author'],
                    body = page_entity['body'],
                    title = page_entity['title'],
                    button_name = page_entity['button_name'])
      is_new_page = True

    # We have some extra work to do for a post's path
    path =  page_entity.get('path')
    if path:
      new_path = self._Pathify(path)
    elif not entity.path:
      new_path = self._Pathify(entity.title)
    else:  # Really this is an existing path
      new_path = entity.path

    # Force the path to be unique
    existing_pages = self.GetPagesByPath(new_path)
    if existing_pages.count():
      path_warning = False
      if is_new_page:
        path_warning = True
      else:
        for page in existing_pages:
          if page.key() != entity.key():
            path_warning = True

      if path_warning:
        return page_entity, {'path': path_warning}
    entity.path = new_path

    # We also have to do some extra work with is_published
    if 'is_published' in page_entity:
      entity.is_published = page_entity['is_published']
      if page_entity['is_published'] and not entity.publish_date:
        entity.publish_date = self._datetime.now()
    else:
      entity.is_published = False

    if not is_new_page:
      entity.updated_by = page_entity['author']

    # The rest we can handle normally
    for property in entity.properties():
      if property in page_entity:
        if property in ['path', 'is_published', 'publish_date', 'updated_by']:
          continue
        setattr(entity, property, page_entity[property])

    entity.put()
    return entity, None

  def GetPagesByPath(self, path):
    """Given a path, returns the corresponding page.

    Args:
      path: path string for the desired page

    Returns:
      gql query object
    """
    return Page.gql('WHERE path = :1', path)

  def DeletePageByKey(self, key):
    """Given a key to a page entity, deletes to page.

    Args:
      key: key for the desired page entity
    """
    if self._admin:
      page = self.GetPage(key)
      page.delete()

  def GetPublishedPages(self):
    """Returns all published pages."""
    return Page.gql('WHERE is_published = :1', True)
